<?php

	/**
	 * File
	 *
	 * Provides static methods for the creation, copying, deletion, moving, and opening of files.
	 *
	 * @package      Core
	 * @subpackage   IO
	 *
     * @license      GNU Lesser General Public Licence see LICENCE-LGPL file or http://www.gnu.org/licenses/lgpl.html
	 */
	class Core_IO_File
	{

	////////////////////////////////////////////////////////////////////////////
	//                             Static Methods                             //
	////////////////////////////////////////////////////////////////////////////

        /**
         * Measures file size
         *
         * @param       string                  $path : The file to analyse
         * @return      int
         */
        public static function size($path)
        {
            return filesize($path);
        }

        /**
         * Determines whether the file is readable
         *
         * @param       string                  $path
         * @return      boolean
         */
        public static function isReadable($path)
        {
            return (@is_readable($path)) ? true : false;
        }

		/**
		 * Determines whether the specified file exists
		 *
		 * @param		string					$path : The file to check
		 * @return		boolean					true if the path contains the name of an existing file, otherwise, false.
		 */
		public static function exists($path)
		{
			return (@file_exists($path) && @is_file($path)) ? true : false;
		}

		/**
		 * Opens an existing file for reading
		 *
		 * @param		string					$path : The file to be opened for reading
		 * @return		Core_IO_FileStream		A read-only FileStream on the specified path
		 */
		public static function openRead($path)
		{
			return new Core_IO_FileStream($path, Core_IO_FileMode::READ);
		}

		/**
		 * Opens an existing file for writing
		 *
		 * @param		string					$path : The file to be opened for writing
		 * @return		Core_IO_FileStream		An FileStream object on the specified path with Write access
		 */
		public static function openWrite($path)
		{
			return new Core_IO_FileStream($path, Core_IO_FileMode::WRITE);
		}

		/**
		 * Creates a file stream that appends UTF-8 encoded text to an existing file
		 *
		 * @param		string					$path : The path to the file to append to
		 * @return		Core_IO_FileStream		An file stream that appends UTF-8 encoded text to an existing file
		 */
		public static function appendText($path)
		{
			return new Core_IO_FileStream($path, Core_IO_FileMode::APPEND);
		}

		/**
		 * Opens a text file, reads the content of the file into a string, and then closes the file
		 *
		 * @param		string					$path : The path and name of the file to read
		 * @throws		Core_ArgumentException
		 * @throws		Core_ArgumentNullException
		 * @throws		Core_IO_FileNotFoundException
		 * @return		string					A string  containing the file's content
		 */
		public static function readAll($path)
		{
            if ($path === null) {
				throw new Core_ArgumentNullException('path is a null reference');
			}

            if (strlen(trim($path)) == 0) {
				throw new Core_ArgumentException('path is a zero-length string');
			}

			if (!Core_IO_File::exists($path)) {
				throw new Core_IO_FileNotFoundException('The file specified in path was not found');
			}

			return @file_get_contents($path);
		}

		/**
		 * Opens a text file, reads all lines of the file into a string array, and then closes the file
		 *
		 * @param		string					$path : The path and name of the file to read
		 * @throws		Core_ArgumentException
		 * @throws		Core_ArgumentNullException
		 * @throws		Core_IO_FileNotFoundException
		 * @return		string[]				A string array containing all lines of the file
		 */
		public static function readAllLines($path)
		{
            if ($path === null) {
				throw new Core_ArgumentNullException('path is a null reference');
			}

            if (strlen(trim($path)) == 0) {
				throw new Core_ArgumentException('path is a zero-length string');
			}

//			if (!Core_IO_File::exists($path)) {
//				throw new Core_IO_FileNotFoundException('The file specified in path was not found');
//			}

			return @file($path);
		}

		/**
		 * Creates a new file, write the specified content to the file, and then closes the file.
		 * If the target file already exists, it is overwritten
		 *
		 * @param		string		$path : the path to the file
		 * @param		string		$content : the content to write to the file
		 * @throws		Core_IO_Exception
		 * @throws		Core_ArgumentException
		 * @throws		Core_ArgumentNullException
		 * @throws		Core_IO_DirectoryNotFoundException
		 * @return		void
		 */
		public static function writeAll($path, $content = '')
		{
            if ($path === null) {
				throw new Core_ArgumentNullException('path is a null reference');
			}

            if (strlen(trim($path)) == 0) {
				throw new Core_ArgumentException('path is a zero-length string');
			}

            if ($content === null) {
				throw new Core_ArgumentNullException('content is a null reference');
			}

            $_dirname = @dirname($path);

            if (!@is_writable($_dirname)) {
                // cache_dir not writable, see if it exists
                if (!Core_IO_Directory::exists($_dirname)) {
                    throw new Core_IO_DirectoryNotFoundException('The directory '. $_dirname .' doesn\'t exist.');
                }

                throw new Core_IO_Exception('Unable to write file '. $path .'.');
            }

            // write to tmp file, then rename it to avoid
            // file locking race condition
            $_tmp_file = @tempnam($_dirname, 'wrt');

            $fs = null;

            try {
                $fs = self::openWrite($_tmp_file);
            } catch (Exception $e) {
                $_tmp_file = Core_IO_Path::combine($_dirname,  uniqid('wrt'));

                try {
                    $fs = self::openWrite($_tmp_file);
                } catch (Exception $e) {
                    throw new Core_IO_Exception('An error occurred while writting to '. $path .'.');
                }
            }

            if ($fs instanceof Core_IO_FileStream) {
                $fs->write($content);
                $fs->close();
            }

            // Delete the file if it allready exists.
            // this is needed on Windows because it cannot overwrite files with rename()
            self::delete($path);

            self::move($_tmp_file, $path);
            self::setPermissions($path, 0664);
		}

		/**
		 * Creates a new file, write the specified string array to the file, and then closes the file.
		 * If the target file already exists, it is overwritten
		 *
		 * @param		string					$path : The file to write to
		 * @param		string[]				$contents : The string array to write to the file
		 * @return		void
		 */
		public static function writeAllLines($path, array $contents)
		{
			$fs = new Core_IO_FileStream($path, Core_IO_FileMode::WRITE);

			foreach ($contents as $line) {
				$fs->write($line);
			}

			$fs->close();
		}

		/**
		 * Copies an existing file to a new file
		 *
		 * @param		string					$sourceFileName : The file to copy
		 * @param		string					$destFileName : The name of the destination file. This cannot be a directory
		 * @param		boolean					$overwrite : true if the destination file can be overwritten, otherwise, false
		 * @throws		Core_IO_Exception
		 * @throws		Core_ArgumentException
		 * @throws		Core_ArgumentNullException
		 * @throws		Core_IO_FileNotFoundException
		 * @return		void
		 */
		public static function copy($sourceFileName, $destFileName, $overwrite = false)
		{
            if ($sourceFileName === null) {
				throw new Core_ArgumentNullException('sourceFileName is a null reference');
			}

            if ($destFileName === null) {
				throw new Core_ArgumentNullException('destFileName is a null reference');
			}

            if (strlen(trim($sourceFileName)) == 0) {
				throw new Core_ArgumentException('sourceFileName is a zero-length string');
			}

            if (strlen(trim($destFileName)) == 0) {
				throw new Core_ArgumentException('destFileName is a zero-length string');
			}

            if (@is_dir($sourceFileName)) {
				throw new Core_ArgumentException('sourceFileName specifies a directory');
			}

            if (@is_dir($destFileName)) {
				throw new Core_ArgumentException('destFileName specifies a directory');
			}

			if (!self::exists($sourceFileName)) {
				throw new Core_IO_FileNotFoundException('sourceFileName was not found');
			}

			if (!$overwrite && self::exists($destFileName)) {
				throw new Core_IO_Exception('destFileName already exists');
			}

			if ($sourceFileName == $destFileName) {
				throw new Core_IO_Exception('The sourceFileName and destFileName parameters refer to the same file');
			}

			if (!@copy($sourceFileName, $destFileName)) {
				throw new Core_IO_Exception('An I/O error has occurred');
			}
		}

		/**
		 * Moves a specified file to a new location, providing the option to specify a new file name
		 *
		 * @param		string					$sourceFileName : The name of the file to move
		 * @param		string					$destFileName : The new path for the file
		 * @throws		Core_IO_Exception
		 * @throws		Core_ArgumentException
		 * @throws		Core_ArgumentNullException
		 * @throws		Core_IO_FileNotFoundException
		 * @return		void
		 */
		public static function move($sourceFileName, $destFileName)
		{
            if ($sourceFileName === null) {
				throw new Core_ArgumentNullException('sourceFileName is a null reference');
			}

            if ($destFileName === null) {
				throw new Core_ArgumentNullException('destFileName is a null reference');
			}

            if (strlen(trim($sourceFileName)) == 0) {
				throw new Core_ArgumentException('sourceFileName is a zero-length string');
			}

            if (strlen(trim($destFileName)) == 0) {
				throw new Core_ArgumentException('destFileName is a zero-length string');
			}

			if (!self::exists($sourceFileName)) {
				throw new Core_IO_FileNotFoundException('sourceFileName was not found');
			}

			if (self::Exists($destFileName)) {
				throw new Core_IO_Exception('destFileName already exists');
			}

			if ($sourceFileName == $destFileName) {
				throw new Core_IO_Exception('The sourceFileName and destFileName parameters refer to the same file');
			}

			if (@rename($sourceFileName, $destFileName) === false) {
				throw new Core_IO_Exception('An I/O error occurred while renaming the file');
			}
		}

		/**
		 * Deletes the specified file.
		 * An exception is not thrown if the specified file does not exist
		 *
		 * @param		string					$path : The name of the file to be deleted
		 * @throws		Core_IO_Exception
		 * @throws		Core_ArgumentException
		 * @throws		Core_ArgumentNullException
		 * @return		void
		 */
		public static function delete($path)
		{
            if ($path === null) {
				throw new Core_ArgumentNullException('path is a null reference');
			}

            if (strlen(trim($path)) == 0) {
				throw new Core_ArgumentException('path is a zero-length string');
			}

			if (!self::exists($path)) {
				return;
			}

			if (@is_dir($path)) {
				throw new Core_IO_Exception('path is a directory');
			}

			if (!@unlink($path)) {
				throw new Core_IO_Exception('Unable to delete the file specified by path');
			}
		}

		/**
		 * Returns the inode change time of file
		 * Note: In most Unix filesystems, a file is considered changed when its inode data is changed;
		 * that is, when the permissions, owner, group, or other metadata from the inode is updated
		 *
		 * @param		string					$path : The path to the file
		 * @throws		Core_ArgumentException
		 * @throws		Core_ArgumentNullException
		 * @throws		Core_IO_FileNotFoundException
		 * @return		string
		 */
		public static function getLastModificationTime($path)
		{
            if ($path === null) {
				throw new Core_ArgumentNullException('path is a null reference');
			}

            if (strlen(trim($path)) == 0) {
				throw new Core_ArgumentException('path is a zero-length string');
			}

			if (!self::exists($path)) {
				throw new Core_IO_FileNotFoundException('The file cannot be found');
			}

			return date('d M Y', @filectime($path));
		}

		/**
		 * Returns the date and time the specified file was last accessed
		 *
		 * @param		String					$path : The path to the file
		 * @throws		Core_ArgumentException
		 * @throws		Core_ArgumentNullException
		 * @throws		Core_IO_FileNotFoundException
		 * @return		string
		 */
		public static function getLastAccessTime($path)
		{
            if ($path === null) {
				throw new Core_ArgumentNullException('path is a null reference');
			}

            if (strlen(trim($path)) == 0) {
				throw new Core_ArgumentException('path is a zero-length string');
			}

			if (!self::exists($path)) {
				throw new Core_IO_FileNotFoundException('The file cannot be found');
			}

			return date('d M Y', @filectime($path));
		}

		/**
		 * Returns the mode of the specified file
		 *
		 * @param		string					$path : The path to the file
		 * @throws		Core_IO_Exception
		 * @throws		Core_ArgumentException
		 * @throws		Core_ArgumentNullException
		 * @throws		Core_IO_FileNotFoundException
		 * @return		integer					The octal permissions value
		 */
		public static function getPermissions($path)
		{
            if ($path === null) {
				throw new Core_ArgumentNullException('path is a null reference');
			}

            if (strlen(trim($path)) == 0) {
				throw new Core_ArgumentException('path is a zero-length string');
			}

			if (!self::exists($path)) {
				throw new Core_IO_FileNotFoundException('The file cannot be found');
			}

			$value = 0;
			if (($permissions = @fileperms($path)) === false) {
				throw new Core_IO_Exception('An I/O error has occurred');
			}

			// Owner; User
			$value += (($permissions & 0x0100) ? 0x0100 : 0x0000); //Read
			$value += (($permissions & 0x0080) ? 0x0080 : 0x0000); //Write
			$value += (($permissions & 0x0040) ? 0x0040 : 0x0000); //Execute

			// Group
			$value += (($permissions & 0x0020) ? 0x0020 : 0x0000); //Read
			$value += (($permissions & 0x0010) ? 0x0010 : 0x0000); //Write
			$value += (($permissions & 0x0008) ? 0x0008 : 0x0000); //Execute

			// Global; World
			$value += (($permissions & 0x0004) ? 0x0004 : 0x0000); //Read
			$value += (($permissions & 0x0002) ? 0x0002 : 0x0000); //Write
			$value += (($permissions & 0x0001) ? 0x0001 : 0x0000); //Execute

			// Misc
			$value += (($permissions & 0x40000) ? 0x40000 : 0x0000); //temporary file (01000000)
			$value += (($permissions & 0x80000) ? 0x80000 : 0x0000); //compressed file (02000000)
			$value += (($permissions & 0x100000) ? 0x100000 : 0x0000); //sparse file (04000000)
			$value += (($permissions & 0x0800) ? 0x0800 : 0x0000); //Hidden file (setuid bit) (04000)
			$value += (($permissions & 0x0400) ? 0x0400 : 0x0000); //System file (setgid bit) (02000)
			$value += (($permissions & 0x0200) ? 0x0200 : 0x0000); //Archive bit (sticky bit) (01000)

			return $value;
		}

		/**
		 * Attempts to change the mode of the specified file
		 *
		 * @param		string					$path : The path to the file
		 * @param		integer					$mode : The octal rights for the file (ignored on Windows)
		 * @throws		Core_IO_Exception
		 * @throws		Core_ArgumentException
		 * @throws		Core_ArgumentNullException
		 * @throws		Core_IO_FileNotFoundException
		 * @return		void
		 */
		public static function setPermissions($path, $mode = 0777)
		{
            if ($path === null) {
				throw new Core_ArgumentNullException('path is a null reference');
			}

            if (strlen(trim($path)) == 0) {
				throw new Core_ArgumentException('path is a zero-length string');
			}

			if (!self::exists($path)) {
				throw new Core_IO_FileNotFoundException('The file cannot be found');
			}

			if (!@chmod($path, $mode)) {
				throw new Core_IO_Exception('An I/O error has occurred');
			}
		}
	}
